package core.semantics;

import core.exceptions.ExceptionFormat;
import core.exceptions.SemanticException;
import core.nodes.Node;
import core.semantics.util.ElementDefinition;
import core.semantics.util.GlobalContext;
import core.semantics.util.Import;
import core.semantics.util.ModuleContext;
import core.semantics.util.Procedure;
import core.semantics.util.SpecificContext;
import core.semantics.util.Variable;
import core.types.ArrayType;
import core.types.PrimitiveType;
import core.types.SimpleType;
import core.types.Type;

public class SemanticAnalyzer {

	private GlobalContext globalContext;
	private ModuleContext moduleContext;

	private SpecificContext specificContext;

	public SemanticAnalyzer() {
		this.globalContext = new GlobalContext();
		this.moduleContext = (ModuleContext) (specificContext = new ModuleContext());
	}

	public void recordVars(Object ids, Node typeNode) {
		Object idHead = ids;

		Variable newVar;
		Object[] newBody;
		do {
			newBody = (Object[]) idHead;

			newVar = new Variable(typeNode.getType(), (ElementDefinition) newBody[0]);

			int iDeclaredVar;
			if ((iDeclaredVar = specificContext.getVariables().indexOf(newVar)) != -1) {
				Variable declaredVar = specificContext.getVariables().get(
						iDeclaredVar);

				throw new SemanticException(
						ExceptionFormat.DUPLICATED_VARIABLE, newVar.getLine(),
						newVar.getColumn(), newVar.getName(),
						declaredVar.getLine(), declaredVar.getColumn());
			}

			newVar.setType(typeNode.getType());
			specificContext.addVariable(newVar);
		} while ((idHead = newBody[1]) != null);
	}

	public void recordProcedureVars(Object ids, Node type) {
		recordVars(ids, type);
	}

	public void openProcedureContext() {
		this.specificContext = new SpecificContext();
	}

	public void closeProcedureContext() {
		this.specificContext = moduleContext;
	}

	public void recordImports(Object ids) {
		Object idHead = ids;

		Object[] newBody;
		Import newImport;
		do {
			newBody = (Object[]) idHead;

			newImport = new Import((ElementDefinition) newBody[0]);

			int iDeclaredImport;
			if ((iDeclaredImport = globalContext.getImports()
					.indexOf(newImport)) != -1) {
				Import declaredImport = globalContext.getImports().get(
						iDeclaredImport);

				throw new SemanticException(ExceptionFormat.DUPLICATED_IMPORT,
						newImport.getLine(), newImport.getColumn(),
						newImport.getName(), declaredImport.getLine(),
						declaredImport.getColumn());
			}

			globalContext.addImport(newImport);
		} while ((idHead = newBody[1]) != null);
	}

	public void recordProcedure(ElementDefinition eDefinition,
			Node formalParametersNode) {
		Procedure newProcedure = new Procedure(specificContext.getVariables(),
				formalParametersNode != null ? (SimpleType) formalParametersNode.getType() : null, eDefinition);

		int iDeclaredProcedure;
		if ((iDeclaredProcedure = globalContext.getProcedures().indexOf(
				newProcedure)) != -1) {
			Procedure declaredProcedure = globalContext.getProcedures().get(
					iDeclaredProcedure);

			throw new SemanticException(ExceptionFormat.DUPLICATED_PROCEDURE,
					newProcedure.getLine(), newProcedure.getColumn(),
					newProcedure.getName(), declaredProcedure.getLine(),
					declaredProcedure.getColumn());
		}

		this.globalContext.addProcedure(newProcedure);
	}
	
	
	//-----------------------------------------------------------------------------------------
	
	public Node newNode(String name, int line, int column) {
		return new Node(null, name, line, column);
	}
	
	public Node newNodeFPrimitiveType(Object primitiveType, int line, int column) {
		return new Node(new SimpleType(PrimitiveType.valueOf(String.valueOf(primitiveType))), null,
				line, column);
	}
	
	public Node newNodeFNumber(String numberValue, int line, int column) {
		if (numberValue.contains(".")) {
			Double longReal = Double.parseDouble(numberValue);

			if (longReal >= Float.MIN_VALUE && longReal <= Float.MAX_VALUE) {
				return new Node(new SimpleType(PrimitiveType.REAL), null, line, column);
			}

			return new Node(new SimpleType(PrimitiveType.LONGREAL), null, line, column);
		}

		Long longInt = Long.parseLong(numberValue);

		if (longInt >= Character.MIN_VALUE && longInt <= Character.MAX_VALUE) {
			return new Node(new SimpleType(PrimitiveType.CHAR), null, line, column);
		}

		if (longInt >= Integer.MIN_VALUE && longInt <= Integer.MAX_VALUE) {
			return new Node(new SimpleType(PrimitiveType.INTEGER), null, line, column);
		}

		return new Node(new SimpleType(PrimitiveType.LONGINT), null, line, column);
	}
	
	public Node widerNode(Node firstFactorNode, Node secondFactorNode) {
		PrimitiveType widerType;

		if (secondFactorNode == null) {
			widerType = ((SimpleType) firstFactorNode.getType())
					.getPrimitiveType();
		} else if ((widerType = PrimitiveType.getWiderType(
				((SimpleType) firstFactorNode.getType()).getPrimitiveType(),
				((SimpleType) secondFactorNode.getType()).getPrimitiveType())) == null) {
			throw new RuntimeException();
		}

		((SimpleType) firstFactorNode.getType()).setPrimitiveType(widerType);

		return firstFactorNode;
	}
	
	public void checkVar(Node invokedVar) {
		Variable retrievedVar;
		if ((retrievedVar = specificContext.getVariable(invokedVar.getName())) == null) {
			throw new SemanticException(
					// TODO mensagem somente para variável
					ExceptionFormat.UNDECLARED_VARIABLE_PROCEDURE,
					invokedVar.getName(), (invokedVar.getLine() + 1),
					(invokedVar.getColumn() + 1));
		} else {
			invokedVar.setType(retrievedVar.getType());
		}
	}

	public void checkExpressionsCompability(Node firstExpressionNode, Node secondExpressionNode) {
		if (secondExpressionNode != null
				&& PrimitiveType.getWiderType(((SimpleType) firstExpressionNode.getType()).getPrimitiveType(),
						((SimpleType) secondExpressionNode.getType()).getPrimitiveType()) == null) {
			throw new SemanticException(
					ExceptionFormat.INCOMPATIBLE_EXPRESSIONS,
					firstExpressionNode.getLine() + 1, firstExpressionNode.getColumn() + 1,
					secondExpressionNode.getLine() + 1, secondExpressionNode.getColumn() + 1);
		}
	}
	
	public void checkAssignmentCompability(Node variableNode, Node expressionNode) {
		if (PrimitiveType.getWiderType(
				((SimpleType) variableNode.getType()).getPrimitiveType(),
				((SimpleType) expressionNode.getType()).getPrimitiveType()) == null) {
			throw new SemanticException(
					ExceptionFormat.INCOMPATIBLE_ASSIGNMENT,
					variableNode.getType(), expressionNode.getType(),
					variableNode.getLine() + 1, variableNode.getColumn() + 1,
					expressionNode.getLine() + 1, expressionNode.getColumn() + 1);
		}
	}
	
	public Node getResultantNodeFExpressions(Node firstExpressionNode, Node secondExpressionNode) {
		if (secondExpressionNode != null) {
			((SimpleType) firstExpressionNode.getType()).setPrimitiveType(PrimitiveType.BOOLEAN);
		}
		
		return firstExpressionNode;
	}

	public void checkProcedureVariableImport(Node node) {
		Variable variable;
		Procedure procedure;
		if ((variable = specificContext.getVariable(node.getName())) != null) {
			node.setType(variable.getType());
			return;
		} else if (globalContext.getImport(node.getName()) != null) {
			return;
			//TODO aprimorar comparação de procedure
		} else if ((procedure = globalContext.getProcedure(node.getName())) != null) {
			node.setType(procedure.getReturnType());
			return;
		}
		
		throw new SemanticException(ExceptionFormat.UNDECLARED_VARIABLE_PROCEDURE,
				node.getName(), (node.getLine() + 1),
				(node.getColumn() + 1));
	}
	
	//---------------------------------------------------------------------------------------------
	
	public void checkNotOperation(Node factorNode, int line, int column) {
		if (!((SimpleType) factorNode.getType()).getPrimitiveType().equals(PrimitiveType.BOOLEAN)) {
			throw new SemanticException(
					ExceptionFormat.CANNOT_APPLY_NOT_OPERATION, line + 1, column + 1);
		}
	}

	public Node newArrayType(Node typeNode) {
		typeNode.setType(new ArrayType(typeNode.getType()));
		return typeNode;
	}

}
